#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "sikle.h"


void SPointer_push(SikleState* ss, Stacel* stacel)
{
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp;

  //  printf("SPointer_push(base:%p,sp:%d)\n", es->base, es->sp);
  if(stacel->type == STACEL_POINTER) {
    *(SPointer*)sp = *(SPointer*)stacel;
    sp += sizeof(SPointer);
    STACEL(sp)->type = STACEL_POINTER;
    es->sp += sizeof(SPointer) + sizeof(Stacel);
  }
  else {
    log_msg(LOG_ERROR, "SPointer_push error", NULL);
  }
}

void SPointer_pop(SikleState* ss, Stacel* stacel)
{
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);
  
  if(STACEL(sp)->type == STACEL_POINTER) {
    sp -= sizeof(SPointer);
    *(SPointer*)stacel = *(SPointer*)sp;
    es->sp = sp - es->base;
  }
  else {
    log_msg(LOG_ERROR, "SPointer_pop error", NULL);
  }
}

void SPointer_drop(SikleState* ss)
{
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);
  
  if(STACEL(sp)->type == STACEL_POINTER) {
    es->sp -= sizeof(SPointer) + sizeof(Stacel);
  }
  else {
    log_msg(LOG_ERROR, "SPointer_drop error", NULL);
  }
}

void SPointer_dup(SikleState* ss)
{
  SPointer pointer;
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  if(STACEL(sp)->type == STACEL_POINTER) {
    sp -= sizeof(SPointer);
    pointer = *(SPointer*)sp;

    sp = es->base + es->sp;
    *(SPointer*)sp = pointer;
    sp += sizeof(SPointer);
    STACEL(sp)->type = STACEL_POINTER;
    es->sp += sizeof(SPointer) + sizeof(Stacel);
  }
  else {
    log_msg(LOG_ERROR, "SPointer_dup error", NULL);
  }
}

void SPointer_p(SikleState* ss)
{
  SPointer pointer;
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  if(STACEL(sp)->type == STACEL_POINTER) {
    sp -= sizeof(SPointer);
    pointer = *(SPointer*)sp;

    printf("%p", pointer.value);
  }
  else {
    log_msg(LOG_ERROR, "SPointer_p error", NULL);
  }
}

int SPointer_to_integer(SikleState* ss)
{
  SPointer pointer;
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  if(STACEL(sp)->type == STACEL_POINTER) {
    sp -= sizeof(SPointer);
    pointer = *(SPointer*)sp;

    return (int)(long)(pointer.value);
  }
  else {
    log_msg(LOG_ERROR, "SPointer_p error", NULL);
    return 0;
  }
}

SPString SPointer_to_string(SikleState* ss)
{
  SPointer pointer;
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);
  SPString sps;

  if(STACEL(sp)->type == STACEL_POINTER) {
    char buff[50];

    sp -= sizeof(SPointer);
    pointer = *(SPointer*)sp;

    sprintf(buff, "%p", pointer.value);
    sps = SPString_new(ss->string_pool, buff, strlen(buff));
    
    return sps;
  }
  else {
    log_msg(LOG_ERROR, "SPointer_p error", NULL);
  }

  return sps;
}

int SPointer_to_boolean(SikleState* ss)
{
  SPointer pointer;
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  if(STACEL(sp)->type == STACEL_POINTER) {
    sp -= sizeof(SPointer);
    pointer = *(SPointer*)sp;

    return (int)(long)pointer.value;
  }
  else {
    log_msg(LOG_ERROR, "SPointer_p error", NULL);
    return 0;
  }
}

void SPointer_add(SikleState* ss)
{
  SPointer pointer;
  SInteger integer;
  StacelType type;

  if(get_stacel_type(ss) == STACEL_POINTER) {
    type = stacel_types[STACEL_POINTER];
    SPointer_pop(ss, STACEL(&pointer));
    
    type_check(get_stacel_type(ss), STACEL_INTEGER);
    SPointer_pop(ss, STACEL(&integer));

    pointer.value += integer.value;
    SPointer_push(ss, STACEL(&pointer));
  }
  else {
    log_msg(LOG_ERROR, "SPointer_add error", NULL);
  }
}

void SPointer_sub(SikleState* ss)
{
  SPointer pointer;
  SInteger integer;
  StacelType type;

  if(get_stacel_type(ss) == STACEL_POINTER) {
    type = stacel_types[STACEL_POINTER];
    SPointer_pop(ss, STACEL(&pointer));
    
    type_check(get_stacel_type(ss), STACEL_INTEGER);
    SPointer_pop(ss, STACEL(&integer));

    pointer.value -= integer.value;
    SPointer_push(ss, STACEL(&pointer));
  }
  else {
    log_msg(LOG_ERROR, "SPointer_sub error", NULL);
  }
}

int _SPointer_cmp(SikleState* ss)
{
  SPointer pointer0;
  SPointer pointer1;
  StacelType type;

  if(get_stacel_type(ss) == STACEL_POINTER) {
    type = stacel_types[STACEL_POINTER];
    SPointer_pop(ss, STACEL(&pointer0));
    
    type_check(get_stacel_type(ss), STACEL_POINTER);
    SPointer_dup(ss);
    SPointer_pop(ss, STACEL(&pointer1));
    SPointer_push(ss, STACEL(&pointer0));

    return (int)((long)pointer1.value - (long)pointer0.value);
  }
  else {
    log_msg(LOG_ERROR, "SPointer_cmp error", NULL);
    return 0;
  }
}

void SPointer_equ(SikleState* ss)
{
  SInteger integer;
  StacelType type;

  if(get_stacel_type(ss) == STACEL_POINTER) {
    type = stacel_types[STACEL_INTEGER];

    integer.type = STACEL_INTEGER;
    integer.value = _SPointer_cmp(ss) == 0 ? TRUE : FALSE;
    type.operation->push(ss, STACEL(&integer));
  }
  else {
    log_msg(LOG_ERROR, "SPointer_equ error", NULL);
  }
}

void SPointer_gt(SikleState* ss)
{
  SInteger integer;
  StacelType type;

  if(get_stacel_type(ss) == STACEL_POINTER) {
    type = stacel_types[STACEL_INTEGER];

    integer.type = STACEL_INTEGER;
    integer.value = _SPointer_cmp(ss) > 0 ? TRUE : FALSE;
    type.operation->push(ss, STACEL(&integer));
  }
  else {
    log_msg(LOG_ERROR, "SPointer_gt error", NULL);
  }
}

void SPointer_ngt(SikleState* ss)
{
  SInteger integer;
  StacelType type;

  if(get_stacel_type(ss) == STACEL_POINTER) {
    type = stacel_types[STACEL_INTEGER];

    integer.type = STACEL_INTEGER;
    integer.value = _SPointer_cmp(ss) <= 0 ? TRUE : FALSE;
    type.operation->push(ss, STACEL(&integer));
  }
  else {
    log_msg(LOG_ERROR, "SPointer_ngt error", NULL);
  }
}

void SPointer_lt(SikleState* ss)
{
  SInteger integer;
  StacelType type;

  if(get_stacel_type(ss) == STACEL_POINTER) {
    type = stacel_types[STACEL_INTEGER];

    integer.type = STACEL_INTEGER;
    integer.value = _SPointer_cmp(ss) < 0 ? TRUE : FALSE;
    type.operation->push(ss, STACEL(&integer));
  }
  else {
    log_msg(LOG_ERROR, "SPointer_lt error", NULL);
  }
}

void SPointer_nlt(SikleState* ss)
{
  SInteger integer;
  StacelType type;

  if(get_stacel_type(ss) == STACEL_POINTER) {
    type = stacel_types[STACEL_INTEGER];

    integer.type = STACEL_INTEGER;
    integer.value = _SPointer_cmp(ss) >= 0 ? TRUE : FALSE;
    type.operation->push(ss, STACEL(&integer));
  }
  else {
    log_msg(LOG_ERROR, "SPointer_nlt error", NULL);
  }
}

void SPointer_builtin_ass_d(SikleState* ss)
{
  SPointer pointer;
  SInteger integer;
  StacelType type;

  if(get_stacel_type(ss) == STACEL_INTEGER) {
    type = stacel_types[STACEL_INTEGER];
    type.operation->pop(ss, STACEL(&integer));
    
    type_check(get_stacel_type(ss), STACEL_POINTER);
    SPointer_pop(ss, STACEL(&pointer));

    *(int*)pointer.value = (int)(integer.value & 0xffffffff);
    //printf("pointer: %p, %d\n", pointer.value, integer.value);
    SPointer_push(ss, STACEL(&pointer));
  }
  else {
    log_msg(LOG_ERROR, "SPointer_ass_d error", NULL);
  }
}

void SPointer_builtin_ass_w(SikleState* ss)
{
  SPointer pointer;
  SInteger integer;
  StacelType type;

  if(get_stacel_type(ss) == STACEL_INTEGER) {
    type = stacel_types[STACEL_INTEGER];
    type.operation->pop(ss, STACEL(&integer));
    
    type_check(get_stacel_type(ss), STACEL_POINTER);
    SPointer_pop(ss, STACEL(&pointer));

    *(short*)pointer.value = (short)(integer.value & 0xffff);
    SPointer_push(ss, STACEL(&pointer));
  }
  else {
    log_msg(LOG_ERROR, "SPointer_ass_w error", NULL);
  }
}

void SPointer_builtin_ass_b(SikleState* ss)
{
  SPointer pointer;
  SInteger integer;
  StacelType type;

  if(get_stacel_type(ss) == STACEL_INTEGER) {
    type = stacel_types[STACEL_INTEGER];
    type.operation->pop(ss, STACEL(&integer));
    
    type_check(get_stacel_type(ss), STACEL_POINTER);
    SPointer_pop(ss, STACEL(&pointer));

    *(char*)pointer.value = (char)(integer.value & 0xff);
    SPointer_push(ss, STACEL(&pointer));
  }
  else {
    log_msg(LOG_ERROR, "SPointer_ass_b error", NULL);
  }
}

void SPointer_builtin_get_d(SikleState* ss)
{
  SPointer pointer;
  SInteger integer;
  StacelType type;

  if(get_stacel_type(ss) == STACEL_POINTER) {
    SPointer_dup(ss);
    SPointer_pop(ss, STACEL(&pointer));

    type = stacel_types[STACEL_INTEGER];
    integer.type = STACEL_INTEGER;
    integer.value = *(int*)pointer.value;

    type.operation->push(ss, STACEL(&integer));
  }
  else {
    log_msg(LOG_ERROR, "SPointer_get_d error", NULL);
  }
}

void SPointer_builtin_get_w(SikleState* ss)
{
  SPointer pointer;
  SInteger integer;
  StacelType type;

  if(get_stacel_type(ss) == STACEL_POINTER) {
    SPointer_dup(ss);
    SPointer_pop(ss, STACEL(&pointer));

    type = stacel_types[STACEL_INTEGER];
    integer.type = STACEL_INTEGER;
    integer.value = *(short*)pointer.value;

    type.operation->push(ss, STACEL(&integer));
  }
  else {
    log_msg(LOG_ERROR, "SPointer_get_w error", NULL);
  }
}

void SPointer_builtin_get_b(SikleState* ss)
{
  SPointer pointer;
  SInteger integer;
  StacelType type;

  if(get_stacel_type(ss) == STACEL_POINTER) {
    SPointer_dup(ss);
    SPointer_pop(ss, STACEL(&pointer));

    type = stacel_types[STACEL_INTEGER];
    integer.type = STACEL_INTEGER;
    integer.value = *(char*)pointer.value;

    type.operation->push(ss, STACEL(&integer));
  }
  else {
    log_msg(LOG_ERROR, "SPointer_get_b error", NULL);
  }
}

void SPointer_register_builtin_words(SikleState* ss)
{
  register_builtin_word(ss, "=<", SPointer_builtin_ass_b);
  register_builtin_word(ss, "=<w", SPointer_builtin_ass_w);
  register_builtin_word(ss, "=<d", SPointer_builtin_ass_d);
  register_builtin_word(ss, "=>", SPointer_builtin_get_b);
  register_builtin_word(ss, "=>w", SPointer_builtin_get_w);
  register_builtin_word(ss, "=>d", SPointer_builtin_get_d);
}

StacelOperation pointer_ops = {
  .push = SPointer_push,
  .pop = SPointer_pop,
  .drop = SPointer_drop,
  .dup = SPointer_dup,

  .clone = NULL,
  .register_builtin_words = SPointer_register_builtin_words,

  .to_integer = SPointer_to_integer,
  .to_string = SPointer_to_string,
  .to_boolean = SPointer_to_boolean,

  .p = SPointer_p,

  .add = SPointer_add,
  .sub = SPointer_sub,
  .mul = NULL,
  .div = NULL,

  .gt = SPointer_gt,
  .lt = SPointer_lt,
  .ngt = SPointer_ngt,
  .nlt = SPointer_nlt,
  .equ = SPointer_equ
};
